/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or https://opensource.org/licenses/CDDL-1.0.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

#include <sys/zfs_context.h>
#include <sys/zfs_file.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/vnode.h>
#include <sys/fcntl.h>
#include <sys/mode.h>	/* VTTOIF */

#if defined(_KERNEL)
#include <sys/kobj.h> 
#endif

#ifndef SEEK_SET
#define SEEK_SET        0       /* set file offset to offset */
#define SEEK_CUR        1       /* set file offset to current plus offset */
#define SEEK_END        2       /* set file offset to EOF plus offset */
#endif

extern int modrootloaded;

/*
 * Open file
 *
 * path - fully qualified path to file
 * flags - file attributes O_READ / O_WRITE / O_EXCL
 * fpp - pointer to return file pointer
 *
 * Returns 0 on success underlying error on failure.
 */
int
zfs_file_open(const char *path, int flags, int mode, zfs_file_t **fpp)
{
	char *pathname;
	file_t *fp;

	pathname = kmem_alloc(MAXPATHLEN, KM_SLEEP);
	(void) snprintf(pathname, MAXPATHLEN, "%s%s",
	    (rootdir != NULL) ? "./" : "", path);

	if (!modrootloaded) {
		struct _buf *file;

		file = kobj_open_file(pathname);
		kmem_free(pathname, MAXPATHLEN);

		if (file == (struct _buf *)-1)
			return (ENOENT);

		fp = kmem_zalloc(sizeof (file_t), KM_SLEEP);
		fp->f_vnode = (vnode_t *) file;
		*fpp = (zfs_file_t *)fp;
	} else {
		vnode_t *vp;
		int fflags;
		int err;

		/*
		* O_RDONLY, O_WRONLY, and O_RDWR to the corresponding FREAD and FWRITE.
		*
		* The construct 'flags + FREAD' conveniently maps combinations of
		*/
		fflags = flags + FREAD;

		if ((err = falloc((vnode_t *)NULL, fflags, &fp, NULL)) != 0) {
			kmem_free(pathname, MAXPATHLEN);
			return (err);
		}

		err = vn_openat(pathname, UIO_SYSSPACE, fflags, mode, &vp, 
		    CRCREAT, 0, rootdir, -1);
		kmem_free(pathname, MAXPATHLEN);

		if (err != 0) {
			unfalloc(fp);
			return (err);
		}
		fp->f_vnode = vp;
		mutex_exit(&fp->f_tlock);
		*fpp = (zfs_file_t *)fp;
	}
	return (0);
}

void
zfs_file_close(zfs_file_t *fp)
{
	if (!modrootloaded) {
		kobj_close_file((struct _buf *)fp->f_vnode);
		kmem_free(fp, sizeof (file_t));
	} else {
		mutex_enter(&fp->f_tlock);
		(void) VOP_PUTPAGE(fp->f_vnode, 0, 0, B_INVAL, kcred, NULL);
		(void) VOP_CLOSE(fp->f_vnode, fp->f_flag, 1, 0, kcred, NULL);
		VN_RELE(fp->f_vnode);
		unfalloc(fp);
	}
}

/*
 * Stateful write - use os internal file pointer to determine where to
 * write and update on successful completion.
 *
 * fp -  pointer to file (pipe, socket, etc) to write to
 * buf - buffer to write
 * count - # of bytes to write
 * residp -  pointer to count of unwritten bytes  (if short write)
 *
 * Returns 0 on success errno on failure.
 */
int
zfs_file_write(zfs_file_t *fp, const void *buf, size_t count, ssize_t *residp)
{
	loff_t off;
	ssize_t resid;
	int err;

	off = zfs_file_off(fp);
	err = zfs_file_pwrite(fp, buf, count, off, &resid);

	if (err != 0) {
		return (err);
	}

	off = count - resid;
	if (off != 0) {
		(void) zfs_file_seek(fp, &off, SEEK_CUR);
	}

	if (residp != NULL) {
		*residp = resid;
	} else if (resid != 0) {
		return (EIO);
	}

	return (0);
}

/*
 * Stateless write - os internal file pointer is not updated.
 *
 * fp -  pointer to file (pipe, socket, etc) to write to
 * buf - buffer to write
 * count - # of bytes to write
 * off - file offset to write to (only valid for seekable types)
 * residp -  pointer to count of unwritten bytes
 *
 * Returns 0 on success errno on failure.
 */
int
zfs_file_pwrite(zfs_file_t *fp, const void *buf, size_t count, loff_t off,
    ssize_t *residp)
{
	void *nonconst_buf;
	ssize_t resid;
	int err;

	if (!modrootloaded) {
		return (EOPNOTSUPP);
	}
	/*
	 * FIXME: There is no guarantee that vn_rdwr does not change buf
	 */
	nonconst_buf = (void *) buf;

	err = vn_rdwr(UIO_WRITE, fp->f_vnode, nonconst_buf, count, off,
	    UIO_SYSSPACE, 0, RLIM64_INFINITY, kcred, &resid);

	if (err < 0) {
		return (err);
	}

	if (residp != NULL) {
		*residp = resid;
	} else if (resid != 0) {
		return (EIO);
	}

	return (0);
}

/*
 * Stateful read - use os internal file pointer to determine where to
 * read and update on successful completion.
 *
 * fp -  pointer to file (pipe, socket, etc) to read from
 * buf - buffer to write
 * count - # of bytes to read
 * residp -  pointer to count of unread bytes (if short read)
 *
 * Returns 0 on success errno on failure.
 */
int
zfs_file_read(zfs_file_t *fp, void *buf, size_t count, ssize_t *residp)
{
	loff_t off;
	ssize_t resid;
	int err;

	off = zfs_file_off(fp);
	err = zfs_file_pread(fp, buf, count, off, &resid);

	if (err != 0) {
		return (err);
	}

	off = count - resid;
	if (off != 0) {
		(void) zfs_file_seek(fp, &off, SEEK_CUR);
	}

	if (residp != NULL) {
		*residp = resid;
	} else if (resid != 0) {
		return (EIO);
	}

	return (0);
}

/*
 * Stateless read - os internal file pointer is not updated.
 *
 * fp -  pointer to file (pipe, socket, etc) to read from
 * buf - buffer to write
 * count - # of bytes to write
 * off - file offset to read from (only valid for seekable types)
 * residp -  pointer to count of unwritten bytes (if short write)
 *
 * Returns 0 on success errno on failure.
 */
int
zfs_file_pread(zfs_file_t *fp, void *buf, size_t count, loff_t off,
    ssize_t *residp)
{
	ssize_t resid;
	int err;

	if (!modrootloaded) {
		err = kobj_read_file((struct _buf *)fp->f_vnode, buf, count,
		    off);
		
		if (err < 0) {
			return (EIO);
		}
		if (residp != NULL) {
			*residp = count - err;
		} else if (count != err) {
			return (EIO);
		}
	} else {
		err = vn_rdwr(UIO_READ, fp->f_vnode, buf, count, off,
		    UIO_SYSSPACE, 0, RLIM64_INFINITY, kcred, &resid);

		if (err < 0) {
			return (err);
		}

		if (residp != NULL) {
			*residp = resid;
		} else if (resid != 0) {
			return (EIO);
		}
	}

	return (0);
}

/*
 * lseek - set / get file pointer
 *
 * fp -  pointer to file (pipe, socket, etc) to read from
 * offp - value to seek to, returns current value plus passed offset
 * whence - see man pages for standard lseek whence values
 *
 * Returns 0 on success errno on failure (ESPIPE for non seekable types)
 * Note: implemented only SEEK_CUR, SEEK_END, SEEK_SET
 */
int
zfs_file_seek(zfs_file_t *fp, loff_t *offp, int whence)
{
	offset_t new_off;
	zfs_file_attr_t fattr;
	int err;

	new_off = *offp;

	if (new_off < 0 || new_off > MAXOFFSET_T)
		return (EINVAL);

	switch (whence) {
	case SEEK_CUR:
		new_off += zfs_file_off(fp);
		break;
	
	case SEEK_END:
		err = zfs_file_getattr(fp, &fattr);
		if (err != 0) {
			return (err);
		}
		new_off += fattr.zfa_size;
		break;
	
	case SEEK_SET:
		break;
	
	default:
		return (EINVAL);
	};

	if (modrootloaded) {
		off_t old_off;

		old_off = zfs_file_off(fp);
		err = VOP_SEEK(fp->f_vnode, old_off, &new_off, NULL);
		if (err != 0) {
			return (err);
		}
	}

	fp->f_offset = new_off;
	return (new_off);
}

/*
 * Get file attributes
 *
 * filp - file pointer
 * zfattr - pointer to file attr structure
 *
 * Currently only used for fetching size and file mode.
 *
 * Returns 0 on success or error code of underlying getattr call on failure.
 */
int
zfs_file_getattr(zfs_file_t *fp, zfs_file_attr_t *zfattr)
{
	int err;

	if (!modrootloaded) {
		uint64_t size;

		err = kobj_get_filesize((struct _buf *)fp->f_vnode, &size);
		if (err < 0)
			return (err);

		zfattr->zfa_size = size;
		zfattr->zfa_mode = 0100000; /* regular */
	} else {
		vattr_t vattr;

		vattr.va_mask = AT_SIZE | AT_MODE | AT_TYPE;
		err = VOP_GETATTR(fp->f_vnode, &vattr, 0, kcred, NULL);
		if (err != 0)
			return (err);

		zfattr->zfa_size = vattr.va_size;
		zfattr->zfa_mode = VTTOIF(vattr.va_type) | vattr.va_mode;
	}

	return (0);
}

/*
 * Sync file to disk
 *
 * fp - file pointer
 * flags - O_SYNC and or O_DSYNC
 *
 * Returns 0 on success or error code of underlying sync call on failure.
 */
int
zfs_file_fsync(zfs_file_t *fp, int flags)
{
	int fflags;

	/*
	 * flags FSYNC, FDSYNC and O_SYNC, O_DSYNC are the same
	 */ 
	fflags = flags;

	return VOP_FSYNC(fp->f_vnode, fflags, kcred, NULL);
}

/*
 * fallocate - allocate or free space on disk
 *
 * fp - file pointer
 * mode (non-standard options for hole punching etc)
 * offset - offset to start allocating or freeing from
 * len - length to free / allocate
 *
 * OPTIONAL
 */
int
zfs_file_fallocate(zfs_file_t *fp, int mode, loff_t offset, loff_t len)
{
	flock64_t flck;

	bzero(&flck, sizeof (flck));
	flck.l_type = F_FREESP;
	flck.l_start = offset;
	flck.l_len = len;
	flck.l_whence = 0;

	return (VOP_SPACE(fp->f_vnode, F_FREESP, &flck, 0, 0, kcred, NULL));
}

/*
 * Request current file pointer offset
 *
 * fp - pointer to file
 *
 * Returns current file offset.
 */
loff_t
zfs_file_off(zfs_file_t *fp)
{
	return (fp->f_offset);
}

/*
 * Request file pointer private data
 *
 * fp - pointer to file
 *
 * Returns pointer to file private data.
 */
void *
zfs_file_private(zfs_file_t *fp)
{
	return (NULL);
}

/*
 * unlink file
 *
 * path - fully qualified file path
 *
 * Returns 0 on success.
 *
 * OPTIONAL
 */
int
zfs_file_unlink(const char *path)
{
	int err;

	err = vn_remove(path, UIO_SYSSPACE, RMFILE);

	return (err);
}

/*
 * Get reference to file pointer
 *
 * fd - input file descriptor
 *
 * Returns pointer to file struct or NULL
 */
zfs_file_t *
zfs_file_get(int fd)
{
	return (getf(fd));
}

/*
 * Drop reference to file pointer
 *
 * fd - input file descriptor
 * fp - input file struct pointer
 */
void
zfs_file_put(int fd, zfs_file_t *fp)
{
	(void)fp;
	releasef(fd);
}
